package edu.bbu.license.client.android.communication.services.thrift.files;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

import org.apache.thrift.TException;

import edu.bbu.license.client.android.communication.exceptions.DataBaseErrorException;
import edu.bbu.license.client.android.communication.exceptions.FileDownloadException;
import edu.bbu.license.client.android.communication.exceptions.FileOperationException;
import edu.bbu.license.client.android.communication.exceptions.FileUploadException;
import edu.bbu.license.client.android.communication.exceptions.NoConnectionException;
import edu.bbu.license.client.android.communication.exceptions.TransferInterruptedException;
import edu.bbu.license.client.android.communication.factory.thrift.ThriftCLTFactory;
import edu.bbu.license.client.android.communication.model.DirectoryPOJO;
import edu.bbu.license.client.android.communication.model.FilePOJO;
import edu.bbu.license.client.android.communication.services.FileCLTServices;
import edu.bbu.license.client.android.communication.services.thrift.directories.ThriftDirectory;
import edu.bbu.license.client.android.communication.services.thrift.exceptions.TDatabaseErrorException;
import edu.bbu.license.client.android.communication.services.thrift.exceptions.TFileOperationException;
import edu.bbu.license.client.android.communication.services.thrift.files.utils.ThriftSocketFileObjectInputStreamReader;
import edu.bbu.license.client.android.communication.utils.FileTransferObserver;
import edu.bbu.license.client.android.communication.utils.ThriftExecutor;

/**
 * The thrift client, file services implementation.
 */
public class ThriftFileCLTServices implements FileCLTServices
{		
	/**
	 * Uploading a file to the server.
	 * @param file it contains information about the file.
	 * @throws FileUploadException if the upload failed.
	 */
	public void uploadFile(FilePOJO file, FileTransferObserver observer) throws FileUploadException, TransferInterruptedException
	{
		FileInputStream fileInputStream = file.getFileInputStream();
		
		if(fileInputStream == null)
		{
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileUploadException("File upload failed, missing fileInputStream.");
		}
		
		Socket socket;
		
		try
		{
			socket = new Socket(ThriftCLTFactory.HOST_ADRESS, ThriftCLTFactory.SOCKET_PORT);
		}
		catch (IOException e)
		{
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileUploadException("File upload failed on socket creating.");
		}
		
		SocketFile socketFileObject = new SocketFile(SocketFile.FILE_UPLOAD);
		socketFileObject.setOwner(file.getOwner());
		socketFileObject.setFileName(file.getFileName());
		socketFileObject.setDirectoryName(file.getDirectoryName());
		
		try
		{
			ObjectOutputStream fileObject = new ObjectOutputStream(socket.getOutputStream());
			fileObject.flush();
			fileObject.writeObject(socketFileObject);
			fileObject.flush();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileUploadException("File upload failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileUploadException("File upload failed on file object sending.");
		}
		
		try
		{
			ThriftSocketFileObjectInputStreamReader objectReader = new ThriftSocketFileObjectInputStreamReader(socket.getInputStream());
			SocketReceip receip = (SocketReceip) objectReader.readObject();
			
			if(receip.getStatus() == SocketReceip.ERROR)
			{
				try
				{
					objectReader.close();
					socket.close();
				}
				catch (IOException ex)
				{
					observer.setStatus(FileTransferObserver.CRASHED);
					throw new FileUploadException("File upload failed on socket closing.");
				}
				
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileUploadException("Upload directory does not exists, upload stopped.");
			}
		}
		catch (Exception e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileUploadException("File upload failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileUploadException("File upload failed on file object reading.");
		}
		
		OutputStream outputStream;
		
		try
		{
			outputStream = socket.getOutputStream();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileUploadException("File upload failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileUploadException("File upload failed on getting the socket output stream.");
		}
		
		int i = 0;
		long sent = 0;
		boolean stopped = false;
		int bufferSize = 65536;
		byte[] buffer = new byte[bufferSize];
		
		try
		{			
			while(!stopped && (i = fileInputStream.read(buffer, 0, bufferSize)) != -1)
			{
				outputStream.write(buffer, 0, i);
				
				switch (observer.getStatus())
				{					
					case FileTransferObserver.PAUSED:
					{
						do
						{
							try
							{
								synchronized (observer)
								{
									observer.wait();
								}
							}
							catch (InterruptedException e) { }
							
						}while(observer.getStatus() == FileTransferObserver.PAUSED);
						
					}break;
					
					case FileTransferObserver.STOPPED:
					{
						stopped = true;
						
					}break;
				}
				
				sent += i;
				observer.setProgressPercent((int) ((sent * 100L) / observer.getFileSize()));
			}
			
			outputStream.flush();
			
			socket.shutdownOutput();
			fileInputStream.close();
			
			ThriftSocketFileObjectInputStreamReader objectReader = new ThriftSocketFileObjectInputStreamReader(socket.getInputStream());
			
			try
			{
				SocketReceip receip = (SocketReceip) objectReader.readObject();
				
				if(receip.getStatus() == SocketReceip.ERROR)
				{
					try
					{
						objectReader.close();
						outputStream.close();
						
						socket.close();
					}
					catch (IOException ex)
					{
						observer.setStatus(FileTransferObserver.CRASHED);
						throw new FileUploadException("File upload failed on socket closing.");
					}
					
					observer.setStatus(FileTransferObserver.CRASHED);
					throw new FileUploadException("Upload directory deleted, meanwhile upload processed.");
					
				}
			}
			catch (ClassNotFoundException e)
			{
				e.printStackTrace();
			}
			
			objectReader.close();
			socket.close();
			
			if(!stopped)
			{
				if(sent != observer.getFileSize())
				{
					observer.setStatus(FileTransferObserver.CRASHED);
					throw new TransferInterruptedException("File tranfser interupted.");
				}
				else 
				{
					observer.setStatus(FileTransferObserver.FINISHED);
				}
			}
			else 
			{
				observer.setStatus(FileTransferObserver.STOPPED);
				throw new TransferInterruptedException("File tranfser interupted be user.");
			}
			
		}
		catch (IOException e)
		{
			try
			{
				outputStream.close();
				fileInputStream.close();
				
				socket.close();
			}
			catch (Exception ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileUploadException("File upload failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			
			e.printStackTrace();
			throw new FileUploadException("File upload failed on file read/write.");
		}
	}

	/**
	 * Downloading a file from the server.
	 * @param file it contains information about the file.
	 * @throws FileDownloadException if the download failed.
	 */
	public void downloadFile(FilePOJO file, FileTransferObserver observer) throws FileDownloadException, TransferInterruptedException
	{
		FileOutputStream fileOutputStream = file.getFileOutputStream();
		
		if(fileOutputStream == null)
		{			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileDownloadException("File upload failed, missing fileOutputStream.");
		}
		
		Socket socket;
		
		try
		{
			socket = new Socket(ThriftCLTFactory.HOST_ADRESS, ThriftCLTFactory.SOCKET_PORT);
		}
		catch (IOException e)
		{
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileDownloadException("File download failed on socket creating.");
		}
		
		SocketFile socketFileObject = new SocketFile(SocketFile.FILE_DOWNLOAD);
		socketFileObject.setOwner(file.getOwner());
		socketFileObject.setFileName(file.getFileName());
		socketFileObject.setFileVersion(file.getVersion());
		socketFileObject.setDirectoryName(file.getDirectoryName());
		
		try
		{
			ObjectOutputStream fileObject = new ObjectOutputStream(socket.getOutputStream());
			fileObject.flush();
			fileObject.writeObject(socketFileObject);
			fileObject.flush();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileDownloadException("File download failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileDownloadException("File download failed on file object sending.");
		}
		
		InputStream inputStream;
		
		try
		{
			inputStream = socket.getInputStream();
		}
		catch (IOException e)
		{
			try
			{
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileDownloadException("File download failed on socket closing.");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileDownloadException("File download failed on getting the socket input stream.");
		}
		
		int i = 0;
		int received = 0;
		boolean stopped = false;
		int bufferSize = 65536;
		byte[] buffer = new byte[bufferSize];
		
		try
		{
			while(!stopped && (i = inputStream.read(buffer, 0, bufferSize)) != -1)
			{
				fileOutputStream.write(buffer, 0, i);
				
				switch (observer.getStatus())
				{					
					case FileTransferObserver.PAUSED:
					{
						do
						{
							try
							{
								synchronized (observer)
								{
									observer.wait();
								}
							}
							catch (InterruptedException e) { }
							
						}while(observer.getStatus() == FileTransferObserver.PAUSED);
						
					}break;
					
					case FileTransferObserver.STOPPED:
					{
						stopped = true;
						
					}break;
				}
				
				received += i;
				observer.setProgressPercent((int) ((received * 100L) / observer.getFileSize()));
				
			}

			fileOutputStream.flush();
			
			inputStream.close();
			fileOutputStream.close();
			
			socket.close();
			
			if(!stopped)
			{
				if(received != observer.getFileSize())
				{
					observer.setStatus(FileTransferObserver.CRASHED);
					throw new TransferInterruptedException("File tranfser interupted.");
				}
				else 
				{
					observer.setStatus(FileTransferObserver.FINISHED);
				}
			}
			else 
			{
				observer.setStatus(FileTransferObserver.STOPPED);
				throw new TransferInterruptedException("File tranfser interupted be user.");
			}
			
		}
		catch (IOException e)
		{
			try
			{
				inputStream.close();
				fileOutputStream.close();
				
				socket.close();
			}
			catch (IOException ex)
			{
				observer.setStatus(FileTransferObserver.CRASHED);
				throw new FileDownloadException("File download failed on socket closing..");
			}
			
			observer.setStatus(FileTransferObserver.CRASHED);
			throw new FileDownloadException("File download failed file read/write.");
		}
	}

	/**
	 * Deleting a file from the server.
	 * @param file it contains information about the file.
	 * @throws DataBaseErrorException 
	 */
	public void deleteFile(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException
	{
		ThriftFile thriftFile = new ThriftFile();
		thriftFile.setOwner(filePOJO.getOwner());
		thriftFile.setFileName(filePOJO.getFileName());
		thriftFile.setFileVersion(filePOJO.getVersion());
		thriftFile.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{
			thriftExecutor.open();
			thriftExecutor.getExecutor().deleteFile(thriftFile);
		}
		
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost."); 
		}
		finally
		{
			thriftExecutor.close();
		}
		
	}
	
	/**
	 * Delete a file with all of the versions.
	 * @param file the file information.
	 * @throws NoConnectionException if the connection lost.
	 * @throws FileOperationException if the opertion failed.
	 * @throws DataBaseErrorException 
	 */
	public void deleteAllFileVersions(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException
	{
		ThriftFile thriftFile = new ThriftFile();
		thriftFile.setOwner(filePOJO.getOwner());
		thriftFile.setFileName(filePOJO.getFileName());
		thriftFile.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{
			thriftExecutor.open();
			thriftExecutor.getExecutor().deleteAllFileVersions(thriftFile);
		}
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost."); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}
	
	/**
	 * Getting the last file version.
	 * @param file the file information.
	 * @return the last file version number.
	 * @throws NoConnectionException if the connection lost.
	 * @throws FileOperationException if the opertion failed.
	 * @throws DataBaseErrorException 
	 */
	public int getLastFileVersionNumber(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException
	{
		ThriftFile thriftFile = new ThriftFile();
		thriftFile.setOwner(filePOJO.getOwner());
		thriftFile.setFileName(filePOJO.getFileName());
		thriftFile.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{
			thriftExecutor.open();
			return thriftExecutor.getExecutor().getLastFileVersion(thriftFile);
			
		}
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost."); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}
	
	/**
	 * Getting all the existinf gile version numbers.
	 * @return a list of all file version numbers.
	 * @throws NoConnectionException if the connection lost.
	 * @throws FileOperationException if the opertion failed.
	 * @throws DataBaseErrorException 
	 */
	public ArrayList<Integer> getAllFileVersions(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException
	{
		ThriftFile thriftFile = new ThriftFile();
		thriftFile.setOwner(filePOJO.getOwner());
		thriftFile.setFileName(filePOJO.getFileName());
		thriftFile.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{
			thriftExecutor.open();
			return (ArrayList<Integer>) thriftExecutor.getExecutor().getFileVersionNumbers(thriftFile);
		}
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost."); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}

	/**
	 * Getting a directory content on the server.
	 * @param directoryPOJO if contains information about the server.
	 * @return an ArrayList of String with the file names.
	 * @throws DataBaseErrorException 
	 */
	public ArrayList<String> getFilesFromDirectory(DirectoryPOJO directoryPOJO) throws NoConnectionException, DataBaseErrorException
	{
		ThriftDirectory directory = new ThriftDirectory();
		directory.setOwner(directoryPOJO.getOwner());
		directory.setDirectoryName(directoryPOJO.getDirectoryName());
		directory.setParrentDirectory(directoryPOJO.getParrentDirectory());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{
			thriftExecutor.open();
			return (ArrayList<String>) thriftExecutor.getExecutor().getFilesFromDirectory(directory);
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost."); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}

	/**
	 * Getting the size of file in bytes.
	 * @param filePOJO the filePOJO. It must contain the owner, file name and direcoty name.
	 * @return the file size in bytes.
	 * @throws NoConnectionException if the connection lost.
	 * @throws FileOperationException 
	 */
	public long getFileSize(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException
	{
		ThriftFile file = new ThriftFile();
		file.setOwner(filePOJO.getOwner());
		file.setFileName(filePOJO.getFileName());
		file.setFileVersion(filePOJO.getVersion());
		file.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{			
			thriftExecutor.open();
			return thriftExecutor.getExecutor().getFileSize(file);
		}
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost. "); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}
	
	@Override
	public long getFileUploadDate(FilePOJO filePOJO) throws NoConnectionException, FileOperationException, DataBaseErrorException 
	{
		ThriftFile file = new ThriftFile();
		file.setOwner(filePOJO.getOwner());
		file.setFileName(filePOJO.getFileName());
		file.setFileVersion(filePOJO.getVersion());
		file.setDirectoryName(filePOJO.getDirectoryName());
		
		ThriftExecutor thriftExecutor = ThriftCLTFactory.getExecutor();
		
		try
		{			
			thriftExecutor.open();
			return thriftExecutor.getExecutor().getFileUploadDate(file);
		}
		catch (TFileOperationException e)
		{
			throw new FileOperationException("File not found.");
		}
		catch (TDatabaseErrorException e)
		{
			throw new DataBaseErrorException("Error occured in database.");
		}
		catch (TException e)
		{
			throw new NoConnectionException("Connection lost. "); 
		}
		finally
		{
			thriftExecutor.close();
		}
	}
}
